package cn.melonlib.graphql.cores.comp.engine;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.melonlib.graphql.cores.comp.utils.GraphqlScanner;
import cn.melonlib.graphql.model.enums.GraphqlScalarType;
import cn.melonlib.graphql.model.metadata.GraphqlFieldBean;
import cn.melonlib.graphql.model.metadata.GraphqlFunction;
import cn.melonlib.graphql.model.metadata.GraphqlInput;
import cn.melonlib.graphql.model.metadata.GraphqlView;
import cn.melonlib.graphql.model.support.Connection;
import cn.melonlib.graphql.model.support.GraphqlEdge;
import cn.melonlib.graphql.model.support.PageInfo;
import cn.melonlib.objects.maps.DataMap;
import cn.melonlib.objects.maps.JsonMap;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.schema.*;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.stream.Stream;

@Component
@Lazy(false)
@Getter
public class GraphqlEngine extends ApplicationObjectSupport {

    @Resource
    private GraphqlScanner graphqlScanner;

    private DataMap<Object,GraphQLScalarType> scalars=new DataMap<>();
    {
        Arrays.stream(GraphqlScalarType.values()).forEach(s->{
            scalars.append(s.getScalarType().getName(),s.getScalarType());
            scalars.append(s.getJavaType(),s.getScalarType());
            scalars.append(s.name(),s.getScalarType());
        });
    }

    private JsonMap<JsonMap<GraphQLInputObjectType>> inputs=JsonMap.createMap();

    private JsonMap<JsonMap<GraphQLObjectType>> views=JsonMap.createMap();

    private JsonMap<GraphQL> graphqlContainer=JsonMap.createMap();

    private JsonMap<GraphQLObjectType> queries=JsonMap.createMap();

    private JsonMap<GraphQLObjectType> mutations=JsonMap.createMap();

    private GraphqlDataPackage graphqlDataPackage;

    private List<GraphQLFieldDefinition> queryFunctions=new ArrayList<>();

    private List<GraphQLFieldDefinition> mutationFunctions=new ArrayList<>();

    public ExecutionResult execute(String namespace,String query,Map<String,Object> variables){
        return graphqlContainer.get(StringUtils.defaultIfBlank(namespace,graphqlScanner.getDefaultNamespace())).execute(ExecutionInput.newExecutionInput().query(query).variables(variables).build());
    }

    public void load(List<String> typePackages) throws Exception{
        logger.info(StringUtils.repeat("*", 100));
        this.graphqlDataPackage=this.getApplicationContext().getBeansOfType(GraphqlDataPackage.class)
                .values().stream().findAny().orElseGet(()->new GraphqlDataPackage() {
                });
        String logo= """
                  _____                 _      ____  _        _                     _ _                        \s
                 / ____|               | |    / __ \\| |      | |                   | (_)                       \s
                | |  __ _ __ __ _ _ __ | |__ | |  | | |      | |     ___   __ _  __| |_ _ __   __ _            \s
                | | |_ | '__/ _` | '_ \\| '_ \\| |  | | |      | |    / _ \\ / _` |/ _` | | '_ \\ / _` |           \s
                | |__| | | | (_| | |_) | | | | |__| | |____  | |___| (_) | (_| | (_| | | | | | (_| |  _   _   _\s
                 \\_____|_|  \\__,_| .__/|_| |_|\\___\\_\\______| |______\\___/ \\__,_|\\__,_|_|_| |_|\\__, | (_) (_) (_)
                                 | |                                                           __/ |           \s
                                 |_|                                                          |___/            \s
                """;
        logger.info("\r\n"+logo);
        graphqlScanner.scanType(typePackages);
        graphqlScanner.scanFunctions();
        logger.info(StringUtils.repeat("*", 50)+"：完成包扫描");
        graphqlScanner.getViewTypes().forEach((key, value) -> {
            logger.info("装载命名空间"+key+"的显示类型：");
            JsonMap<GraphQLObjectType> nsTypes = views.get(key, JsonMap::createMap);
            Stream.concat( graphqlScanner.getAllViewTypes().entrySet().stream(),value.entrySet().stream())
                            .forEach(e->{
                                nsTypes.append(e.getKey(), createViewType(e.getValue()));
                                logger.info("\t"+e.getKey());
                            });
        });
        graphqlScanner.getInputTypes().forEach((key, value) -> {
            logger.info("装载命名空间"+key+"的输入类型：");
            JsonMap<GraphQLInputObjectType> nsTypes = inputs.get(key, JsonMap::createMap);
            Stream.concat( graphqlScanner.getAllInputTypes().entrySet().stream(),value.entrySet().stream())
                    .forEach(e -> {
                nsTypes.append(e.getKey(), createInputType(e.getValue()));
                logger.info("\t"+e.getKey());
            });
        });
        logger.info(StringUtils.repeat("*", 50)+"：完成类型注入");
        graphqlScanner.getQueries().forEach((name, value) -> {
            GraphQLObjectType graphQLObjectType=GraphQLObjectType.newObject().name("Query")
                    .description("查询")
                    .fields(
                            value.entrySet().stream().map(e->{
                                GraphqlFunction graphqlFunction=e.getValue();
                                logger.info("注册功能：" + graphqlFunction.getName());
                                GraphQLFieldDefinition.Builder builder=GraphQLFieldDefinition.newFieldDefinition().name(graphqlFunction.getName()).description(graphqlFunction.getComment());
                                builder.type((GraphQLOutputType)packageType(name,graphqlFunction));
                                builder.arguments(graphqlFunction.getArguments().stream().map(
                                        arg-> GraphQLArgument.newArgument().name(arg.getName())
                                                .description(arg.getComment())
                                                .type((GraphQLInputType) refType(arg.getType(), arg.isRequired(), arg.isRequiredBody(), arg.isList())).build()
                                ).toList());
                                GraphQLFieldDefinition graphQLFieldDefinition= builder.build();
                                this.queryFunctions.add(graphQLFieldDefinition);
                                return graphQLFieldDefinition;
                            }).toList()
                    ).build();
            queries.append(name,graphQLObjectType);
        });

        graphqlScanner.getMutations().forEach((name,value)->{
            GraphQLObjectType graphQLObjectType=GraphQLObjectType.newObject().name("Mutation")
                    .description("更新")
                    .fields(
                            value.entrySet().stream().map(e->{
                                GraphqlFunction graphqlFunction=e.getValue();
                                GraphQLFieldDefinition.Builder builder=GraphQLFieldDefinition.newFieldDefinition().name(graphqlFunction.getName()).description(graphqlFunction.getComment())
                                        .type((GraphQLOutputType) refType(graphqlFunction.getType(),false,false, graphqlFunction.isList()));
                                builder.arguments(graphqlFunction.getArguments().stream().map(
                                        arg-> GraphQLArgument.newArgument().name(arg.getName())
                                                .description(arg.getComment())
                                                .type((GraphQLInputType) refType(arg.getType(), arg.isRequired(), arg.isRequiredBody(), arg.isList())).build()
                                ).toList());
                                GraphQLFieldDefinition graphQLFieldDefinition=builder.build();
                                this.mutationFunctions.add(graphQLFieldDefinition);
                                return graphQLFieldDefinition;
                            }).toList()
                    ).build();
            mutations.append(name,graphQLObjectType);
        });

        Stream.concat(queries.keySet().stream(),mutations.keySet().stream()).distinct().forEach(n->{
            logger.info("正在装载引擎:" + n);
            GraphQLSchema.Builder builder=GraphQLSchema.newSchema();
            builder.additionalTypes(new HashSet<>(scalars.values()));
            builder.additionalTypes(new HashSet<>(views.get(n,JsonMap::createMap).values()));
            builder.additionalTypes(new HashSet<>(inputs.get(n,JsonMap::createMap).values()));
            GraphQLObjectType query=queries.get(n);
            if(Objects.nonNull(query)){
                builder.query(query);
            }
            GraphQLObjectType mutation=mutations.get(n);
            if(Objects.nonNull(mutation)){
                builder.mutation(mutation);
            }
            GraphQLCodeRegistry.Builder registryBuilder=GraphQLCodeRegistry.newCodeRegistry();
            graphqlScanner.getFunctions().get(n).values().stream().forEach(graphqlFunction -> {

                registryBuilder.dataFetcher(
                        FieldCoordinates.coordinates( switch (graphqlFunction.getMode()){
                            case QUERY -> "Query";
                            case MUTATION -> "Mutation";
                        },graphqlFunction.getName())
                ,(DataFetcher<?>) environment->{
                            List<Object> args=graphqlFunction.getArguments().stream().map(arg->environment.getArgument(arg.getName())).toList();
                            Object bean=getApplicationContext().getBean(graphqlFunction.getBean());
                            Method m=graphqlFunction.getMethod();
                            List<Object> params=Arrays.stream(m.getParameters()).reduce(new ArrayList<>(),(l,p)->{
                                l.add(transParameterValue(p,args.get(l.size())));
                                return l;
                            },(l,p)->null);
                            Object result=m.invoke(bean,params.toArray());
                            return graphqlDataPackage.packageData(result,graphqlFunction);
                        });
            });
            builder.codeRegistry(registryBuilder.build());
            graphqlContainer.append(n,GraphQL.newGraphQL(builder.build()).build());
            logger.info("装载引擎" + n + "完成！！");
        });
        logger.info(StringUtils.repeat("*", 100));
    }

    protected Object transParameterValue(Parameter parameter,Object value){
        Class<?> cls=parameter.getType();
        if(Objects.isNull(value)){
            return null;
        }
        if(Long.TYPE.isAssignableFrom(cls)||Long.class.isAssignableFrom(cls)){
            return ((Number) value).longValue();
        }
        if(Integer.TYPE.isAssignableFrom(cls)||Integer.class.isAssignableFrom(cls)){
            return ((Number) value).intValue();
        }
        if(Float.TYPE.isAssignableFrom(cls)||Float.class.isAssignableFrom(cls)){
            return ((Number) value).floatValue();
        }
        if(Double.TYPE.isAssignableFrom(cls)||Double.class.isAssignableFrom(cls)){
            return ((Number) value).doubleValue();
        }
        if(Boolean.TYPE.isAssignableFrom(cls)||Boolean.class.isAssignableFrom(cls)){
            return ((Number) value).byteValue();
        }
        if(CharSequence.class.isAssignableFrom(cls)){
            return value.toString();
        }
        if(Map.class.isAssignableFrom(cls)){
            return value;
        }
        return BeanUtil.copyProperties(value,cls);
    }

    public Object packageData(Object object,GraphqlFunction graphqlFunction) throws Exception {
        Map<String,GraphqlDataPackage> beans=this.getApplicationContext().getBeansOfType(GraphqlDataPackage.class);
        if(CollectionUtil.isEmpty(beans)){
            return new GraphqlDataPackage(){}.packageData(object,graphqlFunction);
        }else{
            return beans.values().stream().findFirst().get().packageData(object,graphqlFunction);
        }

    }

    public GraphQLObjectType createViewType(GraphqlView graphqlView){
        return GraphQLObjectType.newObject()
                .name(graphqlView.getName()).description(graphqlView.getComment())
                .fields(
                        graphqlView.getFields().stream().map(this::createField).toList()
                )
                .build();
    }

    public GraphQLInputObjectType createInputType(GraphqlInput graphqlInput){
        return GraphQLInputObjectType.newInputObject()
                .name(graphqlInput.getName()).description(graphqlInput.getComment())
                .fields(graphqlInput.getFields().stream().map(this::createInputField).toList())
                .build();
    }

    public GraphQLFieldDefinition createField(GraphqlFieldBean graphqlFieldBean){

        return GraphQLFieldDefinition.newFieldDefinition().name(graphqlFieldBean.getName())
                        .description(graphqlFieldBean.getComment())
                        .type((GraphQLOutputType) refType(graphqlFieldBean.getType(),graphqlFieldBean.isRequired(), graphqlFieldBean.isRequiredBody(), graphqlFieldBean.isList()))
                .build();
    }

    public GraphQLInputObjectField createInputField(GraphqlFieldBean graphqlFieldBean){

        return GraphQLInputObjectField.newInputObjectField().name(graphqlFieldBean.getName())
                .description(graphqlFieldBean.getComment())
                .type((GraphQLInputType) refType(graphqlFieldBean.getType(), graphqlFieldBean.isRequired(),graphqlFieldBean.isRequiredBody(), graphqlFieldBean.isList()))
                .build();
    }

    public GraphQLType refType(String typeName,boolean required,boolean requiredBody,boolean list){
        GraphQLType type=scalars.get(typeName);
        if(Objects.isNull(type)){
            type=GraphQLTypeReference.typeRef(typeName);
        }
        if(list){
            if(requiredBody) {
                type = GraphQLNonNull.nonNull(type);
            }
            type=GraphQLList.list(type);
            if(required){
                return GraphQLNonNull.nonNull(type);
            }else{
                return type;
            }

        }else if(required){
            return GraphQLNonNull.nonNull(type);
        }else{
            return type;
        }
    }


    public GraphQLType packageType(String namespace,GraphqlFunction graphqlFunction){
        GraphQLType graphQLType=refType(graphqlFunction.getType(),false,false, graphqlFunction.isList());
        return switch (graphqlFunction.getQueryMode()){
            case ONCE -> graphQLType;
            case OFFSET,PAGINATION -> {
                String typeConnectionName=graphqlFunction.getType()+"Connection";
                if(this.views.get(namespace,JsonMap::createMap).containsKey(typeConnectionName)){
                    yield refType(typeConnectionName,false,false,false);
                }
                GraphQLObjectType packageType= GraphQLObjectType.newObject().name(typeConnectionName)
                        .fields(List.of(
                                GraphQLFieldDefinition.newFieldDefinition()
                                        .name("uuid").type(GraphqlScalarType.STRING.getScalarType()).description("查询随机码").build(),
                                GraphQLFieldDefinition.newFieldDefinition()
                                        .name("status").type(GraphqlScalarType.BOOLEAN_TYPE.getScalarType()).description("执行状态").build(),
                                GraphQLFieldDefinition.newFieldDefinition()
                                        .name("totalCount").type(GraphqlScalarType.LONG_TYPE.getScalarType()).description("数据总量").build(),
                                GraphQLFieldDefinition.newFieldDefinition()
                                        .name("edges").type(GraphQLList.list(GraphQLObjectType.newObject().name(graphqlFunction.getType()+"Edge").description("查询结果信息对象")
                                                .fields(List.of(
                                                        GraphQLFieldDefinition.newFieldDefinition().name("node").type(views.get(namespace).get(graphqlFunction.getType())).description("数据节点").build()
                                                )).build())
                                        ).description("查询结果信息").build(),
                                GraphQLFieldDefinition.newFieldDefinition()
                                        .name("pageInfo").type(GraphQLObjectType.newObject().name(graphqlFunction.getType()+"PageInfo").description("分页信息")
                                                .fields(List.of(
                                                        GraphQLFieldDefinition.newFieldDefinition().name("page").type(GraphqlScalarType.INT.getScalarType()).description("数据页码").build(),
                                                        GraphQLFieldDefinition.newFieldDefinition().name("size").type(GraphqlScalarType.INT.getScalarType()).description("显示数据量").build(),
                                                        GraphQLFieldDefinition.newFieldDefinition().name("offset").type(GraphqlScalarType.LONG.getScalarType()).description("便宜量").build(),
                                                        GraphQLFieldDefinition.newFieldDefinition().name("endCursor").type(GraphqlScalarType.STRING.getScalarType()).description("最后游标").build(),
                                                        GraphQLFieldDefinition.newFieldDefinition().name("hasNextPage").type(GraphqlScalarType.BOOLEAN_TYPE.getScalarType()).description("是否有下一页").build()
                                                ))
                                        ).description("查询结果信息").build()

                        )).build();
                this.views.get(namespace,JsonMap::createMap).append(typeConnectionName,packageType);
                yield packageType;
            }
        };
    }

}
